/*-------------------------------------------------------------------------
 *
 * hashvalidate.c
 *	  哈希的操作类验证器。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/access/hash/hashvalidate.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/amvalidate.h"
#include "access/hash.h"
#include "access/htup_details.h"
#include "access/xact.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "parser/parse_coerce.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/regproc.h"
#include "utils/syscache.h"


static bool fc_check_hash_func_signature(Oid fc_funcid, int16 fc_amprocnum, Oid fc_argtype);


/*
 * 哈希操作类的验证器。
 *
 * 此处执行的一些检查覆盖整个操作族，因此在检查每个操作类时是多余的。但它们运行的时间不够长，因此没有太大问题，因此我们接受重复而不是使 amvalidate API 复杂化。
 */
bool hashvalidate(Oid fc_opclassoid)
{
	bool		fc_result = true;
	HeapTuple	fc_classtup;
	Form_pg_opclass fc_classform;
	Oid			fc_opfamilyoid;
	Oid			fc_opcintype;
	char	   *fc_opclassname;
	HeapTuple	fc_familytup;
	Form_pg_opfamily fc_familyform;
	char	   *fc_opfamilyname;
	CatCList   *fc_proclist,
			   *fc_oprlist;
	List	   *fc_grouplist;
	OpFamilyOpFuncGroup *fc_opclassgroup;
	List	   *fc_hashabletypes = NIL;
	int			fc_i;
	ListCell   *fc_lc;

	/* 获取操作类信息 */
	fc_classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(fc_opclassoid));
	if (!HeapTupleIsValid(fc_classtup))
		elog(ERROR, "cache lookup failed for operator class %u", fc_opclassoid);
	fc_classform = (Form_pg_opclass) GETSTRUCT(fc_classtup);

	fc_opfamilyoid = fc_classform->opcfamily;
	fc_opcintype = fc_classform->opcintype;
	fc_opclassname = NameStr(fc_classform->opcname);

	/* 获取操作族信息 */
	fc_familytup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(fc_opfamilyoid));
	if (!HeapTupleIsValid(fc_familytup))
		elog(ERROR, "cache lookup failed for operator family %u", fc_opfamilyoid);
	fc_familyform = (Form_pg_opfamily) GETSTRUCT(fc_familytup);

	fc_opfamilyname = NameStr(fc_familyform->opfname);

	/* 获取操作族的所有操作符和支持函数 */
	fc_oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(fc_opfamilyoid));
	fc_proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(fc_opfamilyoid));

	/* 检查单个支持函数 */
	for (fc_i = 0; fc_i < fc_proclist->n_members; fc_i++)
	{
		HeapTuple	fc_proctup = &fc_proclist->members[fc_i]->tuple;
		Form_pg_amproc fc_procform = (Form_pg_amproc) GETSTRUCT(fc_proctup);

		/*
		 * 所有哈希函数都应与匹配的左右类型注册
		 */
		if (fc_procform->amproclefttype != fc_procform->amprocrighttype)
		{
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s contains support function %s with different left and right input types",
							fc_opfamilyname, "hash",
							format_procedure(fc_procform->amproc))));
			fc_result = false;
		}

		/* 检查过程编号和函数签名 */
		switch (fc_procform->amprocnum)
		{
			case HASHSTANDARD_PROC:
			case HASHEXTENDED_PROC:
				if (!fc_check_hash_func_signature(fc_procform->amproc, fc_procform->amprocnum,
											   fc_procform->amproclefttype))
				{
					ereport(INFO,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("operator family \"%s\" of access method %s contains function %s with wrong signature for support number %d",
									fc_opfamilyname, "hash",
									format_procedure(fc_procform->amproc),
									fc_procform->amprocnum)));
					fc_result = false;
				}
				else
				{
					/* 记住我们可以哈希的类型 */
					fc_hashabletypes =
						list_append_unique_oid(fc_hashabletypes,
											   fc_procform->amproclefttype);
				}
				break;
			case HASHOPTIONS_PROC:
				if (!check_amoptsproc_signature(fc_procform->amproc))
					fc_result = false;
				break;
			default:
				ereport(INFO,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("operator family \"%s\" of access method %s contains function %s with invalid support number %d",
								fc_opfamilyname, "hash",
								format_procedure(fc_procform->amproc),
								fc_procform->amprocnum)));
				fc_result = false;
				break;
		}
	}

	/* 检查单个操作符 */
	for (fc_i = 0; fc_i < fc_oprlist->n_members; fc_i++)
	{
		HeapTuple	fc_oprtup = &fc_oprlist->members[fc_i]->tuple;
		Form_pg_amop fc_oprform = (Form_pg_amop) GETSTRUCT(fc_oprtup);

		/* 检查是否只存在允许的策略编号 */
		if (fc_oprform->amopstrategy < 1 ||
			fc_oprform->amopstrategy > HTMaxStrategyNumber)
		{
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s contains operator %s with invalid strategy number %d",
							fc_opfamilyname, "hash",
							format_operator(fc_oprform->amopopr),
							fc_oprform->amopstrategy)));
			fc_result = false;
		}

		/* 哈希不支持 ORDER BY 操作符 */
		if (fc_oprform->amoppurpose != AMOP_SEARCH ||
			OidIsValid(fc_oprform->amopsortfamily))
		{
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
							fc_opfamilyname, "hash",
							format_operator(fc_oprform->amopopr))));
			fc_result = false;
		}

		/* 检查操作符签名 --- 所有哈希策略相同 */
		if (!check_amop_signature(fc_oprform->amopopr, BOOLOID,
								  fc_oprform->amoplefttype,
								  fc_oprform->amoprighttype))
		{
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s contains operator %s with wrong signature",
							fc_opfamilyname, "hash",
							format_operator(fc_oprform->amopopr))));
			fc_result = false;
		}

		/* 每种数据类型应有相关的哈希函数 */
		if (!list_member_oid(fc_hashabletypes, fc_oprform->amoplefttype) ||
			!list_member_oid(fc_hashabletypes, fc_oprform->amoprighttype))
		{
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s lacks support function for operator %s",
							fc_opfamilyname, "hash",
							format_operator(fc_oprform->amopopr))));
			fc_result = false;
		}
	}

	/* 现在检查操作符/函数组的不一致性 */
	fc_grouplist = identify_opfamily_groups(fc_oprlist, fc_proclist);
	fc_opclassgroup = NULL;
	foreach(fc_lc, fc_grouplist)
	{
		OpFamilyOpFuncGroup *fc_thisgroup = (OpFamilyOpFuncGroup *) lfirst(fc_lc);

		/* 记住与测试操作类完全匹配的组 */
		if (fc_thisgroup->lefttype == fc_opcintype &&
			fc_thisgroup->righttype == fc_opcintype)
			fc_opclassgroup = fc_thisgroup;

		/*
		 * 如果此数据类型对的操作符似乎不完整，则提出投诉（暗示我们有哈希函数但没有操作符）。
		 */
		if (fc_thisgroup->operatorset != (1 << HTEqualStrategyNumber))
		{
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s is missing operator(s) for types %s and %s",
							fc_opfamilyname, "hash",
							format_type_be(fc_thisgroup->lefttype),
							format_type_be(fc_thisgroup->righttype))));
			fc_result = false;
		}
	}

	/* 检查原名操作类是否受支持 */
	/* （如果组存在，我们已经在上面充分检查过） */
	if (!fc_opclassgroup)
	{
		ereport(INFO,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("operator class \"%s\" of access method %s is missing operator(s)",
						fc_opclassname, "hash")));
		fc_result = false;
	}

	/*
	 * 如果操作族没有其支持的数据类型的所有可能组合的条目，则提出投诉。虽然缺少跨类型操作符并不致命，但坚持所有内置哈希操作族是完整的似乎是合理的。
	 */
	if (list_length(fc_grouplist) !=
		list_length(fc_hashabletypes) * list_length(fc_hashabletypes))
	{
		ereport(INFO,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("operator family \"%s\" of access method %s is missing cross-type operator(s)",
						fc_opfamilyname, "hash")));
		fc_result = false;
	}

	ReleaseCatCacheList(fc_proclist);
	ReleaseCatCacheList(fc_oprlist);
	ReleaseSysCache(fc_familytup);
	ReleaseSysCache(fc_classtup);

	return fc_result;
}


/*
 * 我们需要一个自定义版本的 check_amproc_signature，因为核心哈希操作类定义中存在各种黑客。
 */
static bool fc_check_hash_func_signature(Oid fc_funcid, int16 fc_amprocnum, Oid fc_argtype)
{
	bool		fc_result = true;
	Oid			fc_restype;
	int16		fc_nargs;
	HeapTuple	fc_tp;
	Form_pg_proc fc_procform;

	switch (fc_amprocnum)
	{
		case HASHSTANDARD_PROC:
			fc_restype = INT4OID;
			fc_nargs = 1;
			break;

		case HASHEXTENDED_PROC:
			fc_restype = INT8OID;
			fc_nargs = 2;
			break;

		default:
			elog(ERROR, "invalid amprocnum");
	}

	fc_tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_funcid));
	if (!HeapTupleIsValid(fc_tp))
		elog(ERROR, "cache lookup failed for function %u", fc_funcid);
	fc_procform = (Form_pg_proc) GETSTRUCT(fc_tp);

	if (fc_procform->prorettype != fc_restype || fc_procform->proretset ||
		fc_procform->pronargs != fc_nargs)
		fc_result = false;

	if (!IsBinaryCoercible(fc_argtype, fc_procform->proargtypes.values[0]))
	{
		/*
		 * 一些内置哈希操作类通过使用与操作类数据类型不同但物理上兼容的哈希函数来作弊。在某些情况下，即使是“二进制强制”检查也会失败，因为没有相关的强制转换。为了目前，解决方法是拥有一个允许情况的列表。测试特定的函数标识，而不仅仅是它的输入类型，因为 hashvarlena() 采用 INTERNAL，允许任何这样的函数似乎太可怕了。
		 */
		if ((fc_funcid == F_HASHINT4 || fc_funcid == F_HASHINT4EXTENDED) &&
			(fc_argtype == DATEOID ||
			 fc_argtype == XIDOID || fc_argtype == CIDOID))
			 /* 好的，允许使用 hashint4() */ ;
		else if ((fc_funcid == F_HASHINT8 || fc_funcid == F_HASHINT8EXTENDED) &&
				 (fc_argtype == XID8OID))
			 /* 好的，允许使用 hashint8() */ ;
		else if ((fc_funcid == F_TIMESTAMP_HASH ||
				  fc_funcid == F_TIMESTAMP_HASH_EXTENDED) &&
				 fc_argtype == TIMESTAMPTZOID)
			 /* 好的，允许使用 timestamp_hash() */ ;
		else if ((fc_funcid == F_HASHCHAR || fc_funcid == F_HASHCHAREXTENDED) &&
				 fc_argtype == BOOLOID)
			 /* 好的，允许使用 hashchar() */ ;
		else if ((fc_funcid == F_HASHVARLENA || fc_funcid == F_HASHVARLENAEXTENDED) &&
				 fc_argtype == BYTEAOID)
			 /* 好的，允许使用 hashvarlena() */ ;
		else
			fc_result = false;
	}

	/* 如果函数具有第二个参数，则必须用于 64 位盐。 */
	if (fc_nargs == 2 && fc_procform->proargtypes.values[1] != INT8OID)
		fc_result = false;

	ReleaseSysCache(fc_tp);
	return fc_result;
}

/*
 * 用于向哈希操作族添加操作符/函数的预检查函数。
 */
void hashadjustmembers(Oid fc_opfamilyoid,
				  Oid fc_opclassoid,
				  List *fc_operators,
				  List *fc_functions)
{
	Oid			fc_opcintype;
	ListCell   *fc_lc;

	/*
	 * 哈希操作符和所需的支持函数如果是跨类型的，始终是操作族的“松散”成员。如果它们不是跨类型的，我们更倾向于将其绑定到适当的操作类...但如果用户没有创建一个，我们无法这样做，只能回退到使用操作族依赖关系。（在这种情况下，我们不能强制创建一个操作类，因为留下一个不完整的操作类会很糟糕。抛出错误是另一种不理想的选择。）
	 *
	 * 这种行为导致了一种转储/重载风险，因为恢复对象的顺序可能会影响我们最终得到的依赖关系。pg_dump现有的行为将在大多数情况下保留依赖选择，但如果一个跨类型操作符被紧密地绑定到一个操作类中，则不然。那反正是个错误，所以默默地“修复”它也不是太糟糕。
	 *
	 * 可选的支持函数始终是“松散”的家族成员。
	 *
	 * 为了避免重复查找，我们记住最近使用的操作类的输入类型。
	 */
	if (OidIsValid(fc_opclassoid))
	{
		/* 在创建操作类时，需要CCI来查看pg_opclass行 */
		CommandCounterIncrement();
		fc_opcintype = get_opclass_input_type(fc_opclassoid);
	}
	else
		fc_opcintype = InvalidOid;

	/*
	 * 我们几乎一样地处理操作符和支持函数，因此不必重复这个代码块，只需合并列表。
	 */
	foreach(fc_lc, list_concat_copy(fc_operators, fc_functions))
	{
		OpFamilyMember *fc_op = (OpFamilyMember *) lfirst(fc_lc);

		if (fc_op->is_func && fc_op->number != HASHSTANDARD_PROC)
		{
			/* 可选支持过程，因此始终是软性家族依赖 */
			fc_op->ref_is_hard = false;
			fc_op->ref_is_family = true;
			fc_op->refobjid = fc_opfamilyoid;
		}
		else if (fc_op->lefttype != fc_op->righttype)
		{
			/* 跨类型，因此始终是软性家族依赖 */
			fc_op->ref_is_hard = false;
			fc_op->ref_is_family = true;
			fc_op->refobjid = fc_opfamilyoid;
		}
		else
		{
			/* 不是跨类型；是否有合适的操作类？ */
			if (fc_op->lefttype != fc_opcintype)
			{
				/* 避免重复这个昂贵的查找，即使它失败 */
				fc_opcintype = fc_op->lefttype;
				fc_opclassoid = opclass_for_family_datatype(HASH_AM_OID,
														 fc_opfamilyoid,
														 fc_opcintype);
			}
			if (OidIsValid(fc_opclassoid))
			{
				/* 对操作类的硬性依赖 */
				fc_op->ref_is_hard = true;
				fc_op->ref_is_family = false;
				fc_op->refobjid = fc_opclassoid;
			}
			else
			{
				/* 我们被卡住了，因此对操作族建立软性依赖 */
				fc_op->ref_is_hard = false;
				fc_op->ref_is_family = true;
				fc_op->refobjid = fc_opfamilyoid;
			}
		}
	}
}
